6.3 Die Methoden in einer abgeleiteten Klasse
 
Klassen werden abgeleitet, um aus einer Klasse eine spezialisiertere mit weiteren, spezifischen Verhaltensweisen zu entwickeln. Dazu können in Subklassen neue Methoden implementiert werden, die über die der Basisklasse hinausgehen und Verhaltensweisen beschreiben, in der sich die Subklasse von ihrer Basisklasse unterscheidet. Die Methode Draw der Klasse GraphicCircle ist ein Beispiel dazu.
Die von der abgeleiteten Klasse aus der Basisklasse geerbten Methoden müssen nicht im Verhältnis 1:1 übernommen werden, sondern können auf abweichende Bedürfnisse oder Anforderungen angepasst werden. Dazu bietet das objektorientierte Konzept von C# das Verdecken (Ausblenden) und die Methodenüberladung an.
6.3.1 Geerbte Methoden mit »new« verdecken
 
Vom Verdecken oder Ausblenden einer Basisklassenmethode wird gesprochen, wenn in der abgeleiteten Klasse eine Methode implementiert wird,
|
die den gleichen Namen und |
|
eine identische Parameterliste |
besitzt wie eine Methode in der Basisklasse und diese durch eine eigene Implementierung vollständig ersetzt. Das ist beispielsweise der Fall, wenn die Implementierung in der Basisklasse für Objekte vom Typ der abgeleiteten Klasse falsch oder nicht wünschenswert ist. Wird eine Basisklassenmethode in der abgeleiteten Klasse ausgeblendet, wird beim Aufruf der Methode auf Objekte vom Typ der Subklasse immer die verdeckende Version ausgeführt. Entscheidend für das Verdecken einer geerbten Methode ist die Ergänzung der Methodendefinition um den Modifizierer new. Dem C#-Compiler teilen wir damit unsere Absicht mit.
Zur Verdeutlichung nehmen wir an, in der Klasse ClassA wäre die parameterlose Methode TestMethod enthalten:
| class ClassA {
|
| public void TestMethod() {
|
| Console.WriteLine("ClassA.TestMethod()");
|
| }
|
| }
|
Die Klasse ClassB wird aus ClassA abgeleitet. Nach den Regeln der Vererbung wird mit
| class ClassB : ClassA {/* ... */}
|
zum Ausdruck gebracht, dass alle nicht private definierten Mitglieder von ClassA nun auch zu Mitgliedern von ClassB werden.
Man kann sich vorstellen, dass die ClassB sehr wohl ein Interesse an einer Methode namens TestMethod hat – allerdings mit einer anderen Implementierung. In der abgeleiteten Klasse muss daher die geerbte Methode der Basisklasse ausgeblendet und typspezifisch neu codiert werden. Wir können jetzt in ClassB mit
| class ClassB : ClassA {
|
| public new void TestMethod() {
|
| Console.WriteLine("ClassB.TestMethod()");
|
| }
|
| }
|
eine gleichnamige Methode mit identischer Signierung bereitstellen. Da sowohl der Name als auch die Parameterliste identisch mit der aus ClassA geerbten Methode sind, verdeckt TestMethod in ClassB die geerbte.
Wird TestMethod auf ein Objekt vom Typ ClassB mit
| ClassB obj = new ClassB();
|
| obj.TestMethod();
|
aufgerufen, wird die in der abgeleiteten Klasse definierte Methode ausgeführt, was im Befehlsfenster zu der Ausgabe
führt.
| In gleicher Weise, wie eine geerbte Instanzmethode in einer ableitenden Klasse verdeckt werden kann, lassen sich mit new auch Eigenschaften, Felder und statische Komponenten einer Basisklasse ausblenden und durch eine typspezifische Implementierung ersetzen.
|
Verdecken statischer Methoden
Zu einem interessanten Ergebnis führt der new-Modifizierer, wenn in der Basisklasse eine Methode statisch definiert und in der abgeleiteten Klasse versucht wird, das statische Mitglied der Basisklasse durch ein Instanzmember zu ersetzen, beispielsweise:
| class ClassA {
|
| public static void TestMethod(){...}
|
| }
|
| class ClassB : ClassA {
|
| public new void TestMethod(){...}
|
| }
|
Wird ClassB instanziert, kann auf eine Referenz vom Typ ClassB die Instanzmethode aufgerufen werden und auf den Klassenbezeichner ClassB die statische Methode, die in der Basisklasse definiert ist und sich an die abgeleitete Klasse vererbt:
| ClassB obj = new ClassB();
|
| obj.TestMethod();
|
| ClassB.TestMethod();
|
Etwas anders sieht das Ergebnis aus, wenn in der Basisklasse eine Methode als Instanzmethode definiert ist, die in der ableitenden Klasse durch eine statische Methode verdeckt wird:
| class ClassA {
|
| public void TestMethod() {...}
|
| }
|
| class ClassB : ClassA {
|
| public static new void TestMethod() {...}
|
| }
|
Auf die Typangabe ClassB kann auf die verdeckende, statische Version der Methode zugegriffen werden:
Instanziieren wir allerdings die Klasse ClassB, wird uns in der Intellisense-Auswahlliste die Instanzmethode angeboten, so dass wir zunächst ungestraft die folgende Anweisung schreiben können:
Erst der Compiler stellt fest, dass für ein Objekt vom Typ ClassB die Instanzmethode TestMethod nicht im Gültigkeitsbereich definiert ist, und bricht die Kompilierung mit einer Fehlermeldung ab.
Die Sichtbarkeit eines verdeckenden Klassenmitglieds
Zugriffsmodifizierer beschreiben die Sichtbarkeit eines Klassenmitglieds. Ein public deklariertes Mitglied ist über die Grenzen der aktuellen Assemblierung hinaus bekannt, während der Modifizierer internal die Sichtbarkeit auf die aktuelle Assemblierung beschränkt. private Klassenmitglieder hingegen sind nur in der definierenden Klasse sichtbar.
Ein verdeckendes Member muss nicht zwangsläufig denselben Zugriffsmodifizierer haben wie das Member der Basisklasse, das verdeckt wird. Deshalb darf die öffentliche Methode TestMethod der ClassA in der erbenden Klasse ClassB durch eine private deklarierte Methode ausgeblendet werden.
| class ClassA {
|
| public void TestMethod() {
|
| Console.WriteLine("ClassA.TestMethod()");
|
| }
|
| }
|
| class ClassB : ClassA {
|
| private new void TestMethod() {
|
| Console.WriteLine("ClassB.TestMethod()");
|
| }
|
| }
|
Dies führt dazu, dass die verdeckende Methode nur innerhalb von ClassB sichtbar ist, während der Aufruf von TestMethod auf ein Objekt vom Typ ClassB zu der am nächsten liegenden, gleichnamigen öffentlichen Methode in der Vererbungslinie führt:
| ClassB obj = new ClassB();
|
| obj.TestMethode();
|
Im Befehlsfenster wird
ausgegeben. Wir können daraus die Schlussfolgerung ziehen:
| Das vollständige Ausblenden eines geerbten Mitglieds durch Privatisierung ist nicht möglich.
|
Zugriff auf Basisklassenmethoden mit »base«
Obwohl man die geerbte Methode einer Basisklasse verdeckt, um eine gleichnamige Methode mit klassenspezifischer Verhaltensweise in der abgeleiteten Klasse bereitzustellen, kann es unter Umständen sinnvoll sein, den Code aus der Basisklassenmethode zu nutzen, um deren Verhalten in der abgeleiteten Klasse zu übernehmen und passend zu erweitern.
Stellen wir uns dazu ein einfaches Beispiel vor. In der Klasse BaseClass sei die Methode GetNumbers definiert, die mit dem Zufallszahlengenerator Random zwei Zahlen im Bereich zwischen einschließlich 0 und 10 ermittelt und diese in Form eines Arrays an den Benutzer der Klasse zurückgibt.
| class BaseClass {
|
| public int[] GetNumbers() {
|
| int[] arr = new int[2];
|
| Random rnd = new Random();
|
| for(int i = 0; i <= 1; i++)
|
| arr[i] = rnd.Next(11);
|
| return arr;
|
| }
|
| }
|
Nehmen wir an, dass eine Klasse, die aus BaseClass abgeleitet wird, eine Methode GetNumbers enthalten soll, die ebenfalls zwei Zufallszahlen aus demselben Zahlenbereich erzeugen soll, jedoch mit dem Unterschied, dass das 0-indizierte Element grundsätzlich immer die kleinere der beiden Zahlen enthalten soll.
Wir könnten in der Methode der abgeleiteten Klasse denselben Code, wie er in der Methode der Basisklasse enthalten ist, noch einmal schreiben – aber sehr elegant ist diese Lösung nicht. Besser ist es, mit dem Schlüsselwort base auf das entsprechende Member in der Basisklasse zuzugreifen und dessen Verhalten zu übernehmen:
| class DerivedClass : BaseClass {
|
| public new int[] GetNumbers() {
|
| int[] arr = base.GetNumbers();
|
| if(arr[0] > arr[1]) {
|
| int temp;
|
| temp = arr[1];
|
| arr[1] = arr[0];
|
| arr[0] = temp;
|
| }
|
| return arr;
|
| }
|
| }
|
Wir hatten base schon im Zusammenhang mit der Konstruktorverkettung kennen gelernt. Hier wird der Einsatz durch den Aufruf eines Mitglieds der direkten Basisklasse gezeigt:
| int[] arr = base.GetNumbers();
|
GetNumbers in der Basisklasse liefert als Rückgabewert ein Array vom Typ int, der in arr entgegengenommen wird. Falls die Reihenfolge nicht den Anforderungen entspricht, wird zunächst die Hilfsvariable temp deklariert, die eines der beiden Array-Elemente während der Umschichtung der Werte kurzfristig zwischenspeichern soll.
6.3.2 Überladen einer Basisklassenmethode
 
Oft ist es notwendig, die von einer Basisklasse geerbten Methoden in der Subklasse zu überladen, um ein Objekt vom Typ der Subklasse an speziellere Anforderungen anzupassen. Von einer Methodenüberladung wird bekanntlich gesprochen, wenn sich zwei gleichnamige Methoden einer Klasse nur in ihrer Parameterliste unterscheiden. Derselbe Begriff hat sich geprägt, wenn eine geerbte Methode in der Subklasse nach den Regeln der Methodenüberladung ergänzt werden muss.
Im folgenden Beispiel ist die Klasse BaseClass definiert, die eine Methode namens GetValue veröffentlicht, die einen int-Wert entgegennimmt und im geschützten Feld intVar speichert:
| class BaseClass {
|
| protected int intVar;
|
| public void GetValue(int x) {
|
| intVar = x;
|
| }
|
| }
|
Die Klasse DerivedClass beerbt die Klasse BaseClass, implementiert allerdings eine von der geerbten Methode GetValue abweichende Parameterliste und überlädt diese:
| class DerivedClass : BaseClass {
|
| private long lngVar;
|
| public void GetValue(int x, long y) {
|
| intVar = x;
|
| lngVar = y;
|
| }
|
| }
|
Wird mit
| DerivedClass obj = new DerivedClass();
|
ein Objekt vom Typ der abgeleiteten Klasse erzeugt, kann auf dessen Referenz mit zwei Methoden operiert werden, z.B.:
| obj.GetValue(17);
|
| obj.GetValue(2,3);
|
|